package net.st.service;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import name.fraser.neil.plaintext.diff_match_patch;
import net.htmlparser.jericho.Element;
import net.htmlparser.jericho.Source;
import net.st.config.StConfig;
import net.st.meta.SearchDataMeta;
import net.st.model.SearchData;
import net.st.util.DataUtil;

import org.jaxen.FunctionCallException;
import org.jaxen.JaxenException;
import org.jaxen.Navigator;
import org.jaxen.XPath;
import org.jaxen.jericho.DocumentNavigator;
import org.jaxen.jericho.JerichoXPath;
import org.slim3.datastore.Datastore;
import org.slim3.jsp.Functions;
import org.slim3.util.BeanUtil;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.mail.MailService.Message;
import com.google.appengine.api.mail.MailServiceFactory;

public class SearchDataService {

	private static Logger logger = Logger.getLogger(SearchDataService.class
			.getName());

	private SearchDataMeta b = SearchDataMeta.get();
	private diff_match_patch dmp = new diff_match_patch();

	public SearchData get(Key key) {
		return Datastore.get(b, key);
	}

	public SearchData get(Key key, Long version) {
		return Datastore.get(b, key, version);
	}

	public List<SearchData> getAll() {
		return Datastore.query(b).asList();
	}

	public void insert(SearchData SearchData) {
		Transaction tx = Datastore.beginTransaction();
		Datastore.put(tx, SearchData);
		tx.commit();
	}

	public SearchData update(Key key, Long version, Map<String, Object> input) {
		Transaction tx = Datastore.beginTransaction();
		SearchData SearchData = Datastore.get(tx, b, key, version);
		BeanUtil.copy(input, SearchData);
		Datastore.put(tx, SearchData);
		tx.commit();
		return SearchData;
	}

	public SearchData update(SearchData entity) {
		Map<String, Object> input = new HashMap<String, Object>();
		BeanUtil.copy(entity, input);
		return update(entity.getKey(), entity.getVersion(), input);
	}

	public void delete(Key key, Long version) {
		Transaction tx = Datastore.beginTransaction();
		SearchData SearchData = Datastore.get(tx, b, key, version);
		Datastore.delete(tx, SearchData.getKey());
		tx.commit();
	}

	public void refresh(Key key) throws MalformedURLException, JaxenException,
			IOException {
		List<SearchData> items = new ArrayList<SearchData>();
		SearchData e = get(key);
		if (e != null) {
			items.add(e);
			refresh(items);
		}
	}

	public void refresh() throws MalformedURLException, IOException,
			JaxenException {
		refresh(getValidData(StConfig.SEARCH_DATA_LIMIT));
	}

	public void refresh(List<SearchData> items) throws MalformedURLException,
			IOException, JaxenException {
		List<SearchData> updateSearchDataList = new ArrayList<SearchData>();
		for (SearchData e : items) {
			String url = e.getUrl();
			String utf8 = URLEncoder.encode(e.getQuery(), "UTF-8");
			url = url.replace(StConfig.TAG_QUERY, utf8);
			Source source = getHtmlSource(url);
			String newHtmlData = getHtmlData(source, e.getNewHtmlDataXPath());
			if (DataUtil.isNotNull(newHtmlData)) {
				String newMd5 = getMd5(source, e.getMd5XPath());
				if (e.getMd5() == null || !e.getMd5().equals(newMd5)) {
					e.setNewFlag(StConfig.NEW_FLAG_NEW);
					updateSearchDataList.add(e);
				}

				// 새로운 데이터가 아니여도 갱신
				e.setOldHtmlData(e.getNewHtmlData());
				e.setNewHtmlData(newHtmlData);
				e.setDiffHtmlData(getDiffHtmlData(e.getOldHtmlData(),
						e.getNewHtmlData()));
				e.setMd5(newMd5);
				e.setRefreshDate(new Date());
				update(e);
			}
		}

		if (updateSearchDataList.size() > 0) {
			sendMail(updateSearchDataList);
		}
	}

	/**
	 * 
	 * @param url
	 * @return
	 * @throws FunctionCallException
	 */
	public Source getHtmlSource(String url) throws FunctionCallException {
		Navigator navigator = DocumentNavigator.getInstance();
		Source source = (Source) navigator.getDocument(url);
		return source;
	}

	public String getHtmlData(Source source, String xpath)
			throws JaxenException {
		StringBuilder sb = new StringBuilder();

		Navigator navigator = DocumentNavigator.getInstance();
		XPath expr = new JerichoXPath(xpath, navigator);
		Object result = expr.evaluate(source);

		logger.info("source.getEncoding()=" + source.getEncoding());
		if (result instanceof Element) {
			sb.append(DataUtil.localString(result.toString()));
		} else if (result instanceof List) {
			@SuppressWarnings({ "rawtypes" })
			List items = (List) result;
			for (Object o : items) {
				sb.append(DataUtil.localString(o.toString()));
			}
		}

		return sb.toString();
	}

	/**
	 * HTML데이터를 취득
	 * 
	 * @param url
	 * @param xpath
	 * @return
	 * @throws JaxenException
	 */
	public String getHtmlData(String url, String xpath) throws JaxenException {
		Source source = getHtmlSource(url);
		return getHtmlData(source, xpath);
	}

	/**
	 * md5취득
	 * 
	 * @param source
	 * @param md5XPath
	 * @return
	 * @throws JaxenException
	 */
	public String getMd5(Source source, String md5XPath) throws JaxenException {
		return DataUtil.encryptString(getHtmlData(source, md5XPath));
	}

	/**
	 * get diff html
	 * 
	 * @param oldHtmlData
	 * @param newHtmlData
	 * @return
	 */
	public String getDiffHtmlData(String oldHtmlData, String newHtmlData) {
		return dmp.diff_prettyHtml(dmp.diff_main(
				DataUtil.getEmptyString(oldHtmlData),
				DataUtil.getEmptyString(newHtmlData)));
	}

	public void sendMail(List<SearchData> updateSearchDataList)
			throws IOException {

		StringBuilder sb = new StringBuilder();
		sb.append(StConfig.ST_URL + "\n\n");
		for (SearchData e : updateSearchDataList) {
			sb.append(e.getEngineName());
			sb.append(" : ");
			sb.append(StConfig.ST_URL + "/data/show/" + Functions.h(e.getKey()));
			String utf8 = URLEncoder.encode(e.getQuery(), "UTF-8");
			sb.append("\n" + e.getUrl().replace(StConfig.TAG_QUERY, utf8));
			sb.append("\n\n");
		}

		String subject = "나고룸 - 데이터가 갱신되었습니다.";
		String messageBody = sb.toString();
		Message msg = new Message();
		msg.setSender("gosoochaja@gmail.com");
		msg.setTo("gosoochaja@i.softbank.jp");
		msg.setSubject(subject);
		msg.setTextBody(messageBody);

		MailServiceFactory.getMailService().send(msg);
	}

	/**
	 * 가장오래 갱신되지 않은 데이터를 검색
	 * 
	 * @param limit
	 * @return
	 */
	public List<SearchData> getValidData(int limit) {
		return Datastore.query(SearchData.class).sort(b.updateDate.asc)
				.limit(limit).asList();
	}
}